﻿using System;
using System.Diagnostics;

namespace Jodell.Transport.Modbus
{
    public class ModbusResolver : IModbusResolver
    {
        #region [Fields]
        private readonly byte[] dataBuffer;
        private int dataCount;
        private int recCount;
        private int recDataLength;
        private int currentPos;
        private ModbusStatus status;
        private ModbusRecInfo info;
        #endregion

        public ModbusResolver()
        {
            dataBuffer = new byte[512];
        }
        public ModbusResolver(int bufferSize)
        {
            dataBuffer = new byte[bufferSize];
        }

        #region [Functions]
        private ModbusRecInfo Resolve()
        {
            while (currentPos < recCount)
            {
                byte c = dataBuffer[currentPos++];
                switch (this.status)
                {
                    case ModbusStatus.SlaveNo:
                        info = new ModbusRecInfo();
                        info.Ret = false;
                        this.status = ModbusStatus.Command;
                        break;
                    case ModbusStatus.Command:
                        if ((c >> 7) == 1)
                        {
                            //发生错误
                        }
                        else
                        {
                            if (c == (byte)ModbusFunctionCode.ReadHoldingRegisters || c == (byte)ModbusFunctionCode.ReadInputRegisters)
                            {
                                this.status = ModbusStatus.DataLength;
                            }
                            else if (c == (byte)ModbusFunctionCode.WriteMultipleRegisters)
                            {
                                this.status = ModbusStatus.RegestAddressH;
                            }
                            else if (c == (byte)ModbusFunctionCode.WriteFileRecord)
                            {
                                this.status = ModbusStatus.DataLength;
                            }
                            else
                            {
                                throw new Exception("未知命令字");
                            }
                        }
                        break;
                    case ModbusStatus.RegestAddressH:
                        this.info.RegestAddress = (UInt16)(c << 8);
                        this.status = ModbusStatus.RegestAddressL;
                        break;
                    case ModbusStatus.RegestAddressL:
                        this.info.RegestAddress = (UInt16)(c & 0xFF);
                        this.status = ModbusStatus.RegestLengthH;
                        ; break;
                    case ModbusStatus.RegestLengthH:
                        this.info.RegestLength = (UInt16)(c << 8);
                        this.status = ModbusStatus.RegestLengthL;
                        break;
                    case ModbusStatus.RegestLengthL:
                        this.info.RegestLength = (UInt16)(c & 0xFF);
                        this.status = ModbusStatus.CrcH;
                        break;
                    case ModbusStatus.DataLength:
                        this.recDataLength = 0;
                        this.dataCount = c;
                        this.info.RecData = new byte[this.dataCount];
                        this.status = ModbusStatus.Data;
                        break;
                    case ModbusStatus.Data:
                        this.info.RecData[this.recDataLength++] = c;
                        if (this.recDataLength >= dataCount)
                        {
                            this.status = ModbusStatus.CrcH;
                        }
                        break;
                    case ModbusStatus.CrcH:
                        var crcBuf = BitConverter.GetBytes(CrcUtil.Crc(dataBuffer, 0, currentPos - 1));
                        Array.Reverse(crcBuf);
                        this.info.CrcClc = BitConverter.ToUInt16(crcBuf, 0);
                        this.info.CrcRec = (UInt16)(c << 8);
                        this.status = ModbusStatus.CrcL;
                        break;
                    case ModbusStatus.CrcL:
                        this.info.CrcRec |= c;
                        if (this.info.CrcRec == this.info.CrcClc)
                        {
                            this.info.Ret = true;
                        }
                        else
                        {
                            Debug.WriteLine("************************************** CRC 计算不一致 **************************************");
                            throw new Exception("CRC 计算不一致");
                        }
                        break;
                }
            }
            return this.info;
        }
        public void Reset()
        {
            this.info = null;
            this.status = ModbusStatus.SlaveNo;
            this.dataCount = 0;
            this.recCount = 0;
            this.currentPos = 0;
            this.recDataLength = 0;
        }
        public ModbusRecInfo Add(byte data)
        {
            recCount++;
            dataBuffer[recCount] = data;
            return Resolve();
        }
        public ModbusRecInfo AddRange(byte[] datas, int lenth)
        {
            for (int i = 0; i < lenth; i++)
            {
                dataBuffer[recCount++] = datas[i];
            }
            return Resolve();
        }
        #endregion
    }
}
